using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Windows.Interop;
using ViewOn.AddInsHostContract;
using ViewOn.AddInsHostContract.Media;
using ViewOnCore.Helper.Debug;
using ViewOnCore.HostContract;
using Un4seen.Bass;
using Un4seen.Bass.Misc;
using Un4seen.Bass.AddOn.Fx;
using Un4seen.Bass.AddOn.Tags;
using Un4seen.Bass.AddOn.Cd;

namespace ViewOn.Kernel
{

	static public class BassPlayer
	{

		#region Variables

		//----
		static private int _stream = 0;
		static private object _streamLock = new object();

		static private Un4seen.Bass.BASSTimer _updateTimer;

		static private SYNCPROC _sync;
		static private DSPPROC _myDSPAddr;

		// this local member keeps the amplification level as a float value
		static private float _gainAmplification = 1;

		// device latency in milliseconds
		static private int _deviceLatencyMS = 0;

		//---- Balance
		static public int _balance = 0;

		//---- Mute
		static public bool _isMuted = false;
		static public int _muteVolume = 0;

		//---- Equalizer
		static private float[] _bandsValues = new float[10];
		static private double _preamp = 0;
		static private bool _isEqualizerOn = false;
		static private BASS_FX_DSPPEAKEQ _eq;
		static private BASS_FX_DSPCOMPRESSOR _comp;
		static Un4seen.Bass.Misc.DSP_Gain _gain;
		static private float[] _equalizerCenterFrequencies = new float[] { 30f, 60f, 120f, 250f, 500f, 1000f, 2000f, 4000f, 8000f, 16000f };

		//---- Ticket management
		static private PlayTicket _currentTicket;
		static private object _playTicketLock = new object();

		#endregion

        #region Initialize

        static public void Initialize(int windowHandle)
		{
			//System.Windows.Application.Current.Exit += new System.Windows.ExitEventHandler(Current_Exit);

            Un4seen.Bass.BassNet.Registration("viewon01@viewon.tv", "2X1729118172917");

			try
			{
				if (Un4seen.Bass.Bass.BASS_Init(-1, 44100, BASSInit.BASS_DEVICE_LATENCY, windowHandle, null))
				{
					BASS_INFO info = new BASS_INFO();
					Un4seen.Bass.Bass.BASS_GetInfo(info);
					_deviceLatencyMS = info.latency;
				}
				else
				{
					LogManager.Trace(LogType.CriticalError, "BASS Error : " + Un4seen.Bass.Bass.BASS_ErrorGetCode());
					return;
				}

				Un4seen.Bass.Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_GVOL_STREAM, 50);
				_sync = new SYNCPROC(OnMusicEnded);

				// Allow to play playlist directly (PLS / M3U)
				Un4seen.Bass.Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_NET_PLAYLIST, 2);

				//---- Equalizer values
				_isEqualizerOn = Host.SettingsService.IsEqualizerOn;
				_preamp = Host.SettingsService.Preamp;
				_balance = Host.SettingsService.Balance;
				_bandsValues[0] = Host.SettingsService.Equalizer32;
				_bandsValues[1] = Host.SettingsService.Equalizer64;
				_bandsValues[2] = Host.SettingsService.Equalizer125;
				_bandsValues[3] = Host.SettingsService.Equalizer250;
				_bandsValues[4] = Host.SettingsService.Equalizer500;
				_bandsValues[5] = Host.SettingsService.Equalizer1K;
				_bandsValues[6] = Host.SettingsService.Equalizer2K;
				_bandsValues[7] = Host.SettingsService.Equalizer4K;
				_bandsValues[8] = Host.SettingsService.Equalizer8K;
				_bandsValues[9] = Host.SettingsService.Equalizer16K;

				//---- Prepare the DSP proc
				_myDSPAddr = new DSPPROC(MyDSPGainUnsafe);

				//---- Bass plug ins (.wma)
				int _wmaPlugIn = Bass.BASS_PluginLoad("basswma.dll");

				// .aac .mp4
				int _aacPlugIn = Bass.BASS_PluginLoad("bass_aac.dll");

				// streaming and ripping of CD audio tracks.
				int _cdPlugIn = Bass.BASS_PluginLoad("basscd.dll");

				_updateTimer = new Un4seen.Bass.BASSTimer(50);
				_updateTimer.Tick += new EventHandler(updateTimer_Tick);
			}
			catch (Exception e)
			{
				LogManager.Trace(LogType.CriticalError, "Error during BASS initialization : " + e.Message);
				return;
			}
		}

		#endregion

		#region Close

		static void Current_Exit(object sender, System.Windows.ExitEventArgs e)
		{
			Close();
		}

		static public void Close()
		{
			_updateTimer.Tick -= new EventHandler(updateTimer_Tick);

			Bass.BASS_Stop();
			Bass.BASS_Free();
		}

		#endregion

		#region Properties

		/// <summary>
		/// Between 0 and 100
		/// </summary>
		static public int Volume
		{
			get
			{
				if (_isMuted)
					return _muteVolume;
				return Un4seen.Bass.Bass.BASS_GetVolume();
			}
			set
			{
				if (_isMuted)
					_muteVolume = value;
				else
					Un4seen.Bass.Bass.BASS_SetVolume(value);
			}
		}

		static public int Balance
		{
			set
			{
				_balance = value;
				Bass.BASS_ChannelSetAttributes(_stream, -1, -1, _balance);
				Host.SettingsService.Balance = value;
			}
			get
			{
				return _balance;
			}
		}

		static public double Preamp
		{
			get
			{
				return _preamp;
			}
			set
			{
				if (_gain == null)
					return;

				_gain.Stop();
				_gain.Gain_dBV = value;
				_gain.Start();

				_preamp = value;

				Host.SettingsService.Preamp = value;
			}
		}

		/// <summary>
		/// Between 0 and 100
		/// </summary>
		static public float Position
		{
			get
			{
				if (_stream == 0)
					return 0;

				// position in bytes
				long pos = Bass.BASS_ChannelGetPosition(_stream);

				// length in bytes
				long len = Bass.BASS_ChannelGetLength(_stream);

				return (((float)pos) / len) * 100;
			}
			set
			{
				if (_stream == 0)
					return;

				double position = (value / 100) * Bass.BASS_ChannelGetLength(_stream);

				Un4seen.Bass.Bass.BASS_ChannelSetPosition(_stream, (long)position);
			}
		}

		static public TimeSpan TotalTime
		{
			get
			{
				// position in bytes
				long pos = Bass.BASS_ChannelGetPosition(_stream);

				// length in bytes
				long len = Bass.BASS_ChannelGetLength(_stream);

				// the total time length
				float totaltime = Bass.BASS_ChannelBytes2Seconds(_stream, len);

				// the elapsed time length
				float elapsedtime = Bass.BASS_ChannelBytes2Seconds(_stream, pos);

				//float remainingtime = totaltime - elapsedtime;

				return new TimeSpan((long)elapsedtime * TimeSpan.TicksPerSecond);
			}
		}

		static public bool IsMuted
		{
			get
			{
				return _isMuted;
			}
		}

		static public int Stream
		{
			get
			{
				return _stream;
			}
		}

		#endregion

		#region Play

		static public void Play()
		{
			if (_stream != 0 && Un4seen.Bass.Bass.BASS_ChannelPlay(_stream, false))
				_updateTimer.Start();
		}

		#endregion

		#region PlayFile

		static public bool PlayFile(string filename)
		{
			PlayTicket ticket = SetPlayTicket();
			bool resetEqualizer = _isEqualizerOn;

			//---- Create the new stream

			// BASSStream.BASS_STREAM_PRESCAN |
			int streamHandle = Un4seen.Bass.Bass.BASS_StreamCreateFile(filename, 0, 0, BASSStream.BASS_SAMPLE_FLOAT);
			if (streamHandle != 0)
			{
				Un4seen.Bass.Bass.BASS_ChannelSetDSP(streamHandle, _myDSPAddr, 0, 2);
			}
			else
			{
				LogManager.Trace(LogType.CriticalError, "BASS Error : " + Un4seen.Bass.Bass.BASS_ErrorGetCode());
				return false;
			}

			// Clean the equalizer
			if (resetEqualizer)
				IsEqualizerOn = false;

			//Un4seen.Bass.Bass.BASS_ErrorGetCode()
			//Un4seen.Bass.BASSErrorCode

			Un4seen.Bass.Bass.BASS_ChannelSetSync(streamHandle, BASSSync.BASS_SYNC_END, 0L, _sync, 0);

			//---- Cancel the stream
			if (ticket.Cancel)
			{
				Un4seen.Bass.Bass.BASS_StreamFree(streamHandle);
				return false;
			}

			//---- Free the current stream and set new one
			_updateTimer.Stop();

			lock (_streamLock)
			{
				if (_stream != 0)
					Un4seen.Bass.Bass.BASS_StreamFree(_stream);

				_stream = streamHandle;

				// Reset the equalizer
				if (resetEqualizer)
					IsEqualizerOn = true;

				// Reset the balance to the right stream
				Balance = Balance;

				// Play the stream
				Play();
			}

			return true;
		}

		#endregion

		#region PlayUrl

        static public bool PlayUrl(string url)
        {
            PlayTicket ticket = SetPlayTicket();
            bool resetEqualizer = _isEqualizerOn;

            //---- Create the new stream

            // BASSStream.BASS_STREAM_PRESCAN |
            int streamHandle = Un4seen.Bass.Bass.BASS_StreamCreateURL(url, 0, BASSStream.BASS_STREAM_STATUS, null, 0);
            if (streamHandle != 0)
            {
                Un4seen.Bass.Bass.BASS_ChannelSetDSP(streamHandle, _myDSPAddr, 0, 2);
            }
            else
            {
                LogManager.Trace(LogType.CriticalError, "BASS Error : " + Un4seen.Bass.Bass.BASS_ErrorGetCode());
                return false;
            }

            // Clean the equalizer
            if (resetEqualizer)
                IsEqualizerOn = false;

            //Un4seen.Bass.Bass.BASS_ErrorGetCode()
            //Un4seen.Bass.BASSErrorCode

            Un4seen.Bass.Bass.BASS_ChannelSetSync(streamHandle, BASSSync.BASS_SYNC_END, 0L, _sync, 0);

            //---- Cancel the stream
            if (ticket.Cancel)
            {
                Un4seen.Bass.Bass.BASS_StreamFree(streamHandle);
				return false;
            }

            //---- Free the current stream and set new one
            _updateTimer.Stop();

            lock (_streamLock)
            {
                if (_stream != 0)
                    Un4seen.Bass.Bass.BASS_StreamFree(_stream);

                _stream = streamHandle;

                // Reset the equalizer
                if (resetEqualizer)
                    IsEqualizerOn = true;

                // Reset the balance to the right stream
                Balance = Balance;

                // Play the stream
                Play();
            }

			return true;
        }

		#endregion

		#region Stop

		static public void Stop()
		{
			lock (_streamLock)
			{
				//---- Stop the stream
				if (_stream != 0 && Un4seen.Bass.Bass.BASS_ChannelStop(_stream))
					_updateTimer.Stop();

				// Reset the position
				if (_stream != 0)
					Un4seen.Bass.Bass.BASS_ChannelSetPosition(_stream, (long)0L);
			}
		}

		#endregion

		#region Pause

		static public void Pause()
		{
			//---- Stop the stream
			if (_stream != 0 && Un4seen.Bass.Bass.BASS_ChannelPause(_stream))
				_updateTimer.Stop();
		}

		#endregion

		#region MuteInverse

		static public void MuteInverse()
		{
			if (_isMuted)
			{
				// Restore
				//Un4seen.Bass.Bass.BASS_SetVolume(_muteVolume);
                //Bass.BASS_ChannelSetAttribute(_stream, BASSAttribute.BASS_ATTRIB_VOL, _muteVolume);
                Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_GVOL_STREAM, _muteVolume);
			}
			else
			{
				// Save volume and mute
				_muteVolume = Un4seen.Bass.Bass.BASS_GetVolume();
				//Un4seen.Bass.Bass.BASS_SetVolume(0);
                //Bass.BASS_ChannelSetAttribute(_stream, BASSAttribute.BASS_ATTRIB_VOL, 0);
                Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_GVOL_STREAM, 0);
			}

			_isMuted = !_isMuted;
		}

		#endregion

		#region MyDSPGainUnsafe

		// another alternative in using a DSP callback is using UNSAFE code
		// this allows you to use pointers pretty much like in C/C++!!!
		// this is fast, efficient, but is NOT safe (e.g. no overflow handling, no type checking etc.)
		// But also there is no need to Marshal and Copy any data between managed and unmanaged code
		// So be careful!
		// Also note: you need to compile your app with the /unsafe option!
		static unsafe private void MyDSPGainUnsafe(int handle, int channel, System.IntPtr buffer, int length, int user)
		{
			if (_gainAmplification == 1f || length == 0 || buffer == IntPtr.Zero)
				return;

			// length is in bytes, so the number of floats to process is:
			// length / 4 : byte = 8-bit, float = 32-bit
			int l4 = length / 4;

			float* data = (float*)buffer;
			for (int a = 0; a < l4; a++)
			{
				data[a] = data[a] * _gainAmplification;
				// alternatively you can also use:
				// *data = *data * _gainAmplification;
				// data++;  // moves the pointer to the next float
			}
		}

		#endregion

		#region updateTimer_Tick

		static private void updateTimer_Tick(object sender, EventArgs e)
		{
			//---- Check if the stream is still playing
			if (Bass.BASS_ChannelIsActive(_stream) != (int)BASSActive.BASS_ACTIVE_PLAYING)
			{
				// the stream is NOT playing anymore...
				//_updateTimer.Stop();
				return;
			}

            ((MediaService)Host.MediaService).NotifyMediaVisualRefresh();
		}

		#endregion

		#region OnMusicEnded

		static private void OnMusicEnded(int handle, int channel, int data, int user)
		{
            ((MediaService)Host.MediaService).OnMediaEnded();
		}

		#endregion

		#region Get/SetPlayTicket

		static private PlayTicket SetPlayTicket()
		{
			PlayTicket ticket = new PlayTicket();

			lock (_playTicketLock)
			{
				if (_currentTicket != null)
					_currentTicket.Cancel = true;

				_currentTicket = ticket;
			}
			return ticket;
		}

        static public PlayTicket CurrentPlayTicket
        {
            get
            {
                return _currentTicket;
            }
        }

		#endregion

		#region OLD: GetBassCDDriveId

        //static public int GetBassCDDriveId(string path)
        //{
        //    //BASSCDDriveId = ((byte)_drive[0]) - ((byte)'A');
        //    string[] drives = BassCd.BASS_CD_GetDriveDescriptions();
        //    for (int index = 0; index < drives.Length; index++)
        //        if (path[0] == BassCd.BASS_CD_GetDriveLetterChar(index))
        //            return index;

        //    return 0;
        //}

		#endregion

		#region EqualizerOn / EqualizerOff

		static private void EqualizerOff()
		{
			if (!_isEqualizerOn)
				return;
			_isEqualizerOn = false;

			if (_stream == 0)
				return;

			BassFx.BASS_FX_DSP_Remove(_stream, BASSFXDsp.BASS_FX_DSPFX_PEAKEQ);
		}

		static private void EqualizerOn()
		{
			if (_isEqualizerOn)
				return;
			_isEqualizerOn = true;

			if (_eq == null)
				_eq = new BASS_FX_DSPPEAKEQ();

			if (_comp == null)
				_comp = new BASS_FX_DSPCOMPRESSOR();

			_gain = new DSP_Gain(_stream, 10);
			if (_gain != null)
			{
				_gain.Gain_dBV = 0.0;
				_gain.Start();
			}

			object freq = 44100;
			bool ok = Un4seen.Bass.Bass.BASS_ChannelGetAttributes(_stream, freq, null, null);
			Un4seen.Bass.Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_FLOATDSP, true);
			BassFx.BASS_FX_DSP_Set(_stream, BASSFXDsp.BASS_FX_DSPFX_COMPRESSOR, -1);
			_comp.fThreshold = 1f;
			for (int i = 0; i < _equalizerCenterFrequencies.Length; i++)
				ok &= BassFx.BASS_FX_DSP_Set(_stream, BASSFXDsp.BASS_FX_DSPFX_PEAKEQ, 0);

			_eq.lBand = 0;
			ok &= BassFx.BASS_FX_DSP_GetParameters(_stream, _eq);
			if (ok)
			{
				_eq.lFreq = (int)freq;
				_eq.fBandwidth = 1f;
				_eq.fGain = 0f;
				for (int j = 0; j < _equalizerCenterFrequencies.Length; j++)
				{
					_eq.lBand = j;
					_eq.lFreq = (int)freq;
					_eq.fBandwidth = 1f;
					_eq.fCenter = _equalizerCenterFrequencies[j];
					ok &= BassFx.BASS_FX_DSP_SetParameters(_stream, _eq);
				}
			}

			//---- Reset the band values
			for (int index = 0; index < 10; index++)
				SetEqualizerBandValue(index, _bandsValues[index]);

			Preamp = _preamp;
		}

		#endregion

		#region IsEqualizerOn

		static public bool IsEqualizerOn
		{
			get
			{
				return _isEqualizerOn;
			}
			set
			{
				if (value)
					EqualizerOn();
				else
					EqualizerOff();

				Host.SettingsService.IsEqualizerOn = value;
			}
		}

		#endregion

		#region SetEqualizerBandValue / GetEqualizerBandValue

		static public float GetEqualizerBandValue(int index)
		{
			return _bandsValues[index];
		}

		// Gain is a float value and ranges from -15.0 dB to +15.0 dB!
		static public void SetEqualizerBandValue(int band, float gain)
		{
			_bandsValues[band] = gain;

			BASS_FX_DSPPEAKEQ eq = new BASS_FX_DSPPEAKEQ();

			// first get the EQ values of the selected band!
			eq.lBand = band;
			BassFx.BASS_FX_DSP_GetParameters(_stream, eq);

			// and now apply the new gain
			eq.fGain = gain;
			BassFx.BASS_FX_DSP_SetParameters(_stream, eq);

			//---- Update the config
			if (band == 0)
				Host.SettingsService.Equalizer32 = gain;
			else if (band == 1)
				Host.SettingsService.Equalizer64 = gain;
			else if (band == 2)
				Host.SettingsService.Equalizer125 = gain;
			else if (band == 3)
				Host.SettingsService.Equalizer250 = gain;
			else if (band == 4)
				Host.SettingsService.Equalizer500 = gain;
			else if (band == 5)
				Host.SettingsService.Equalizer1K = gain;
			else if (band == 6)
				Host.SettingsService.Equalizer2K = gain;
			else if (band == 7)
				Host.SettingsService.Equalizer4K = gain;
			else if (band == 8)
				Host.SettingsService.Equalizer8K = gain;
			else if (band == 9)
				Host.SettingsService.Equalizer16K = gain;
		}

		#endregion

		#region Cross fading

		/*
        private int _mixerStream = 0;
        private int _streamA = 0;
        private int _streamB = 0;
        private void buttonPlaySource_Click(object sender, System.EventArgs e)
        {
            Bass.BASS_StreamFree(_streamA);
            Bass.BASS_StreamFree(_streamB);
            Bass.BASS_StreamFree(_mixerStream);
            // mixer setup
            // now we need some channels to plug them in...create two decoding sources
            _streamA = Bass.BASS_StreamCreateFile(_fileName, 0, 0, BASSStream.BASS_STREAM_DECODE | BASSStream.BASS_SAMPLE_FLOAT);
            _streamB = Bass.BASS_StreamCreateFile(_fileNameOutput, 0, 0, BASSStream.BASS_STREAM_DECODE | BASSStream.BASS_SAMPLE_FLOAT);
            BASS_CHANNELINFO i = new BASS_CHANNELINFO();
            Bass.BASS_ChannelGetInfo(_streamA, i);
            // this will be the final mixer output stream being played
            _mixerStream = BassMix.BASS_Mixer_StreamCreate(i.freq, 4, BASSStream.BASS_DEFAULT);
            // finally we plug them into the mixer (and upmix it to 4 channels - we assume the source to be stereo)
            bool okA = BassMix.BASS_Mixer_StreamAddChannel(_mixerStream, _streamA, BASSStream.BASS_MIXER_MATRIX | BASSStream.BASS_STREAM_AUTOFREE);
            bool okB = BassMix.BASS_Mixer_StreamAddChannel(_mixerStream, _streamB, BASSStream.BASS_STREAM_AUTOFREE);
            // a matrix for A!		
            float[,] matrixA = new float[4, 2] { // stereo to quad matrix
												{1, 0}, // left front out = left in
												{0, 1}, // right front out = right in
												{1, 0}, // left rear out = left in
												{0, 1} // right rear out = right in
											   };
            // apply the matrix to stream A only
            BassMix.BASS_Mixer_ChannelSetMatrix(_streamA, ref matrixA[0, 0]);
            // just so show how to get it back...
            float[,] matrixGet = new float[4, 2];
            BassMix.BASS_Mixer_ChannelGetMatrix(_streamA, ref matrixGet[0, 0]);
            // mute streamB at the beginning
            this.trackBarCrossFader.Value = -100;
            Bass.BASS_ChannelSetAttributes(_streamB, -1, 0, -101);

            // and play it...
            if (Bass.BASS_ChannelPlay(_mixerStream, false))
                this.label1.Text = "Playing! Use the crossfader...";
        }

        private void trackBarCrossFader_Scroll(object sender, System.EventArgs e)
        {
            // 0 = both to volume max (100) - since we are playing the same track this might sound lounder!
            // - = more to the source
            // + = more to the desting
            int cf = this.trackBarCrossFader.Value;
            int volA = 100;
            if (cf > 0)
                volA -= cf;
            int volB = 100;
            if (cf < 0)
                volB += cf;

            Bass.BASS_ChannelSetAttributes(_streamA, -1, volA, -101);
            Bass.BASS_ChannelSetAttributes(_streamB, -1, volB, -101);
        }

        private void buttonStop_Click(object sender, System.EventArgs e)
        {
            buttonPlaySource.Enabled = false;
            Bass.BASS_ChannelStop(_mixerStream);
            Bass.BASS_StreamFree(_streamA);
            Bass.BASS_StreamFree(_streamB);
            Bass.BASS_StreamFree(_mixerStream);
        }*/

		#endregion

		#region GetFileTotalTime

		static public TimeSpan GetFileTotalTime(string path)
		{
			int streamHandle = Un4seen.Bass.Bass.BASS_StreamCreateFile(path, 0, 0, BASSStream.BASS_SAMPLE_FLOAT);
			if (streamHandle == 0)
			{
				LogManager.Trace(LogType.CriticalError, "BASS Error : " + Un4seen.Bass.Bass.BASS_ErrorGetCode());
				return TimeSpan.Zero;
			}
			
			// position in bytes
			//long pos = Bass.BASS_ChannelGetPosition(streamHandle);

			// length in bytes
			long len = Bass.BASS_ChannelGetLength(streamHandle);

			// the total time length
			float totaltime = Bass.BASS_ChannelBytes2Seconds(streamHandle, len);

			// the elapsed time length
			//float elapsedtime = Bass.BASS_ChannelBytes2Seconds(streamHandle, pos);

			return new TimeSpan((long)totaltime * TimeSpan.TicksPerSecond);
		}
		
		#endregion

	}

	sealed public class PlayTicket
	{
		public bool Cancel = false;
	}
}
